package com.hjq.mallseckill.impl;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.hjq.common.entity.to.SeckillTO;
import com.hjq.common.utils.R;
import com.hjq.mallseckill.entity.to.SeckillSkuRedisTO;
import com.hjq.mallseckill.entity.vo.SecKillWithSkusVO;
import com.hjq.mallseckill.entity.vo.SessionRelationVO;
import com.hjq.mallseckill.entity.vo.SkuInfoTO;
import com.hjq.mallseckill.feign.CouponFeign;
import com.hjq.mallseckill.feign.ProductFeign;
import com.hjq.mallseckill.interceptor.SeckillLoginInterceptor;
import com.hjq.mallseckill.service.SeckillService;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @auther HJQ
 */
@Service
public class SeckillServiceImpl implements SeckillService {
   @Autowired
    CouponFeign couponFeign;
   @Autowired
    ProductFeign productFeign;
    @Autowired
    StringRedisTemplate redisTemplate;
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    RabbitTemplate rabbitTemplate;


    private static final String SESSION_CACHE_PREFIX="seckill:sessions:";
    private static final String SKU_SECKILL_PREFIX="seckill:skus:";
    private static final String SEMAPHORE_SKU_PREFIX="seckill:stock:";


    @Override
    public void updateSeckillIn3Days() {
        R r = couponFeign.upLoadSeckillIn3Days();
        if(r.getCode()==0){
            String string = JSON.toJSONString(r.get("data"));
            List<SecKillWithSkusVO> secKillWithSkusVOS = JSON.parseObject(string, new TypeReference<List<SecKillWithSkusVO>>() {
            });
            //缓存到redis
            //1.缓存活动信息
            saveSession(secKillWithSkusVOS);
            //2.缓存关联的商品信息
            saveSessionSkuRelations(secKillWithSkusVOS);

        }

    }
    public List<SeckillSkuRedisTO> getCurrentSecSkusHandler(){
        return  null;
    }
    @SentinelResource(value = "getCurrentSecSku",blockHandler = "getCurrentSecSkusHandler")
    @Override
    public List<SeckillSkuRedisTO> getCurrentSecSkus() {
        //确定当前时间属于那个常常
        try(Entry entry= SphU.entry("getCurrentSecSkus"))
        {
            long curTime = new Date().getTime();
            Set<String> keys = redisTemplate.keys(SESSION_CACHE_PREFIX + "*");
            for (String key : keys) {
                String[] s = key.replace(SESSION_CACHE_PREFIX, "").split("_");
                long startTime = Long.parseLong(s[0]);
                long endTime = Long.parseLong(s[1]);
                if(curTime>=startTime&&curTime<endTime){
                    //时间区间内
                    List<String> strings = redisTemplate.opsForList().range(key, -100, 100);
                    BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(SKU_SECKILL_PREFIX);
                    Collection stringsSet = new HashSet<>(strings);
                    System.out.println(stringsSet);
                    List<Object> objects = ops.multiGet(stringsSet);
                    if(objects!=null &&objects.size()>0){
                        List<SeckillSkuRedisTO> collect = objects.stream().map(o -> {
                            SeckillSkuRedisTO seckillSkuRedisTO = JSON.parseObject(o.toString(), SeckillSkuRedisTO.class);
                            return seckillSkuRedisTO;
                        }).collect(Collectors.toList());
                        return  collect;
                    }
                    break;
                }
            }
            //获取商品信息
            return null;
        } catch (BlockException e) {
            e.printStackTrace();

            return null;
        }
    }

    @Override
    public SeckillSkuRedisTO getSkuSecInfo(Long skuId) {
        BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SKU_SECKILL_PREFIX);
        Set<String> keys = hashOps.keys();
        if(keys!=null && keys.size()>0){
            String reg="\\d_"+skuId;
            for (String key : keys) {
                if(Pattern.matches(reg,key)){
                    String secSession = hashOps.get(key);
                    SeckillSkuRedisTO skuRedisTO = JSON.parseObject(secSession, SeckillSkuRedisTO.class);
                    Long startTime = skuRedisTO.getStartTime();
                    Long endTime = skuRedisTO.getEndTime();
                    long time = new Date().getTime();
                    if(time>startTime&&time<endTime){

                    }
                    else {
                        skuRedisTO.setRandomCode("0");
                    }
                    return  skuRedisTO;
                }

            }
        }

        return null;
    }

    /**
     * 完成秒杀操作
     * @param killId
     * @param number
     * @param code
     * @return
     */
    @Override
    public String kill(String killId, Integer number, String code) {
        BoundHashOperations<String,String,String> hashOps = redisTemplate.boundHashOps(SKU_SECKILL_PREFIX);
        String s = hashOps.get(killId);
        if(StringUtils.isEmpty(s)){
            return  null;
        }
        SeckillSkuRedisTO skuRedisTO = JSON.parseObject(s, SeckillSkuRedisTO.class);
        //校验合法性
        //时间校验
        Long time = new Date().getTime();
        Long startTime = skuRedisTO.getStartTime();
        Long endTime = skuRedisTO.getEndTime();
        if(time<startTime||time>endTime){
            return  null;
        }
        //独特码校验
        Long skuId1 = Long.parseLong(killId.split("_")[1]);
        String randomCode = skuRedisTO.getRandomCode();
        Long skuId2 = skuRedisTO.getSkuId();
        if(!randomCode.equals(code)|| !skuId1.equals(skuId2)){
            return null;
        }
        //验证购物数量是否合理
        BigDecimal seckillLimit = skuRedisTO.getSeckillLimit();
        if(number>seckillLimit.intValue()){
            return  null;
        }
        //减分布式信号量
        String semaKey = SEMAPHORE_SKU_PREFIX+code;
        RSemaphore semaphore = redissonClient.getSemaphore(semaKey);
        try {
            semaphore.tryAcquire(number,100, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
            return null;
        }
        //秒杀成功
        String timeId = IdWorker.getTimeId();
        SeckillTO seckillTO=new SeckillTO();
        Long memberId = SeckillLoginInterceptor.threadLocal.get().getId();
        seckillTO.setMemberId(memberId);
        seckillTO.setSeckillCount(new BigDecimal(number));
        Long promtionId = Long.parseLong(killId.split("_")[0]);
        seckillTO.setPromotionSessionId(promtionId);
        seckillTO.setSkuId(skuRedisTO.getSkuId());
        seckillTO.setSeckillPrice(skuRedisTO.getSeckillPrice());
        seckillTO.setOrderSn(timeId);
        rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order",seckillTO);



        return timeId;
    }

    private void saveSessionSkuRelations(List<SecKillWithSkusVO> secKillWithSkusVOs) {
        secKillWithSkusVOs.forEach(session->{
            List<SessionRelationVO> relationEntities = session.getRelationEntities();
            BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(SKU_SECKILL_PREFIX);
            relationEntities.forEach(sessionRelationVO -> {
                SeckillSkuRedisTO seckillSkuRedisTO = new SeckillSkuRedisTO();
                String token = UUID.randomUUID().toString().replace("-", "");
                String semaKey = SEMAPHORE_SKU_PREFIX + token;
                //1.sku基本信息
                if(!ops.hasKey(sessionRelationVO.getPromotionSessionId()+"_"+sessionRelationVO.getSkuId())){
                    R info = productFeign.info(sessionRelationVO.getSkuId());
                    if(info.getCode()==0){
                        String string = JSON.toJSONString(info.get("skuInfo"));
                        SkuInfoTO skuInfo = JSON.parseObject(string, SkuInfoTO.class);
                        seckillSkuRedisTO.setSkuInfo(skuInfo);
                    }
                    //2.sku秒杀信息
                    BeanUtils.copyProperties(sessionRelationVO,seckillSkuRedisTO);
                    //3.设置安全信息(redisson设置信号量用来限流)
                    seckillSkuRedisTO.setStartTime(session.getStartTime().getTime());
                    seckillSkuRedisTO.setEndTime(session.getEndTime().getTime());
                    seckillSkuRedisTO.setRandomCode(token);
                    String string = JSON.toJSONString(seckillSkuRedisTO);
                    ops.put(sessionRelationVO.getPromotionSessionId()+"_"+sessionRelationVO.getSkuId(),string);
                    System.out.println("sku基本信息");
                    RSemaphore semaphore = redissonClient.getSemaphore(semaKey);
                    semaphore.trySetPermits(sessionRelationVO.getSeckillCount().intValue());
                    System.out.println("信号量设置完毕");

                }
            });

        });
    }

    private void saveSession(List<SecKillWithSkusVO> secKillWithSkusVOs) {
        for (SecKillWithSkusVO session : secKillWithSkusVOs) {
            long startTime = session.getStartTime().getTime();
            long endTime = session.getEndTime().getTime();
            String key = startTime + "_" + endTime;
            List<String> collect = session.getRelationEntities().stream()
                    .map(sessionRelationVO ->
                            sessionRelationVO.getPromotionSessionId()+"_"+sessionRelationVO.getSkuId())
                    .collect(Collectors.toList());
            if(!redisTemplate.hasKey(SESSION_CACHE_PREFIX+key)){
                redisTemplate.opsForList().leftPushAll(SESSION_CACHE_PREFIX+key,collect);
                System.out.println("场次设置完毕");
            }

        }



    }
}
